/*
 * ParamComplex.java
 * 
 * Copyright 2004 Christoph Csallner and Yannis Smaragdakis.
 */
package edu.gatech.cc.cnc.params;

import static edu.gatech.cc.jcrasher.Assertions.check;
import static edu.gatech.cc.jcrasher.Assertions.notNull;

import java.lang.reflect.Field;
import java.util.Hashtable;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import edu.gatech.cc.cnc.ParseFct;
import edu.gatech.cc.cnc.sym;
import edu.gatech.cc.cnc.plans.stmt.BlockStatementSequence;
import edu.gatech.cc.cnc.plans.stmt.ComplexVariableCreatingBlockStatementSequence;
import edu.gatech.cc.jcrasher.plans.expr.Expression;
import edu.gatech.cc.jcrasher.plans.expr.Variable;
import edu.gatech.cc.jcrasher.plans.stmt.BlockStatement;

/**
 * Represents a non-array, reference type including String 
 * (except the null value).
 * 
 * @author csallner@gatech.edu (Christoph Csallner)
 */
public class ParamComplex<T> extends ParamRefType<T> {
	/* We retrieve values for reference types from
	 * JCrasher's traditional random search. */

  /**
   * Propagate identity from object to object-fields: 
   *   if (o1==o2) then (o1.f==o2.f) for all o.f.
   * To be called before preparing the int solver, because this
   * method might add constraints to the int variables.
   * @param refParams method params that are of reference type.
   * Each of them is the root of its constructor-tree, which is
   * a def-use relation among values, e.g. for a{1, "foo"}:
   *   int i1 = 1;  String s2 = "foo";
   *   A a = new A(i1, s2);
   */
  public static void prepareSolution(List<ParamComplex<?>> refParams) {   
    for (ParamComplex<?> param: refParams) {
      //param.propagateIdentity();  //Simplify has done this already.
      param.equalFieldNames();
    }
  }   
  
	protected final Map<String,Set<String>> elemNames = 
		new Hashtable<String,Set<String>>(); //fieldName --> {fieldNameEsc*}.	

	
	/**
	 * Constructor, to be used for method parameters.
	 */
	public ParamComplex(Class<T> returnType, String name) {
		super(name);
		setType(returnType);
		getEq().addTuple(this);
	}
	

	/**
	 * Implements a subtype constraint.
	 */
	public void specializeTo(Class<? extends T> c) {
		check(c.isArray()==false);
		setType(c);
	}


	
	/**
	 * @return all escNames associated with simpleName, never null.
	 * Each escName is associated with one Param.
	 */
	public String[] getEscNames(String simpleName) {
		notNull(simpleName);
		Set<String> escNames = elemNames.get(simpleName); 
		if (escNames==null) {return new String[0];}
		
		return escNames.toArray(new String[escNames.size()]);
	}
	
	
	/**
	 * Adds (simpleName --> escName) to elemNames and
	 * adds (escName --> Param) to elems. 
	 */
	@Override
	public void addElem(String escName, Param<?> param) {
		/* Hides elems */
		String simpleName = ParseFct.stripEscPostfix(escName);
		Set<String> escNames = elemNames.get(simpleName);		
		
		/* Register escName under name. */
		if (escNames==null) {
			escNames = new LinkedHashSet<String>();
			elemNames.put(simpleName, escNames);
		}
		escNames.add(escName);
		
		/* Add (escName --> param) to elems */
		super.addElem(escName, param);
	}	
	

	/*
	 * Reflect type constraint, possibly overriding previous setting.
	 */
//	public void addTypeConstraint(Class c) {
//		assert (c != null);
//		
//		/* Has the wrappedParam been left unspecified? */
//		if (getType() == null) {             
//			setType(c); 
//			return;	
//		}
//		
//		/* Is c the same or a supertype of the previously set type? */
//		if (c.isAssignableFrom(getType())) {return;}
//		
//		/* Is c a subtype of the previously set type? */
//		if (getType().isAssignableFrom(c)) {
//			setType(c);
//		}
//		else {  //c is neither a sub-type nor a super-type of the wrappedParam.
//			throw new IllegalArgumentException("c="+c.toString()
//				+" is neither a sub-type nor a super-type of the wrappedParam.");
//		}
//	}

	
	
///* Simplify does this already for us.
//* 
//* In this equivalence class,
//* propagate identity from object to object-fields: 
//*   if (o1==o2) then (o1.f==o2.f) for all o.f. 
//* Recurse on fields' equivalence class. */
//protected void propagateIdentity() {
//	/* First, unify all params pi with same name, but different escName. */
//	equalFieldNames();
//	
//	/* Second, propagate equalities between pi-representative and other classes' pi-rep. */
//	Hashtable lastNameParam = new Hashtable(); //cache last seen (name-->param).
//	ParamRefType[] thisEq = (ParamRefType[]) eq.eq(this).toArray(new ParamRefType[eq.eq(this).size()]);
//	for (int iEq=0; iEq<thisEq.length; iEq++) {  //traverse equiv
//		/* add identity to last field seen of this name, for all names. */
//		for (Enumeration eqNames = thisEq[iEq].elemNames.keys(); eqNames.hasMoreElements(); ) {
//			String name = (String) eqNames.nextElement();
//			/* pick one representative of params having the same name. */
//			Param param = thisEq[iEq].getElem(thisEq[iEq].getEscNames(name)[0]);
//			/* Find field of same name in previously traversed part of equivalence class. */
//			Param lastParam = (Param) lastNameParam.get(name);
//			if (lastParam != null) {
//				lastParam.addConstraint(sym.EQ, param);
//			}
//			lastNameParam.put(name, param);  //update with current param.
//		}
//	}
//	
//	/* Third, recurse on fields. */
//	for (Enumeration fields = lastNameParam.elements(); fields.hasMoreElements(); ) {
//		Param field = (Param) fields.nextElement();
//		if (field instanceof ParamRefType) {
//			((ParamRefType) field).propagateIdentity();
//		}
//	}
//}	
	
	
	
	/**
	 * In this equivalence class,
	 * add identity constraints on fields with same name but different
	 * escNames, e.g. |o:1.2|.|i:3.4|==|o:1.2|.|i:4.4|. */
	protected void equalFieldNames() {
		for (ParamRefType<?> equal: getEq().getEqualValues(this)) {  //in each member of equiv class.
			if (! (equal instanceof ParamComplex)) {continue;} //ignore ParamNull

			for (Field field: getType().getDeclaredFields()) {	 //for each field name.
				String[] escNames = ((ParamComplex<?>) equal).getEscNames(field.getName());
				for (int iEsc=1; iEsc<escNames.length; iEsc++) {  //unify all escNames of same name.
					equal.getElemValue(escNames[iEsc-1]).constrain(sym.EQ, equal.getElemValue(escNames[iEsc]));
				}
			}
		}		
	}

	
	
	/* search in eq for field-param */
	private BlockStatement<? extends T> getPlanFromEq(String fieldName) {
		for (ParamRefType<?> p: getEq().getEqualValues(this)) {
			ParamComplex<?> peer = (ParamComplex<?>) p;
			
			String[] escNames = peer.getEscNames(fieldName);
			if (escNames.length>0) {
				return (BlockStatement<? extends T>) peer.getElemValue(escNames[0]).getPlan();
			}
		}
		return null;
	}

  
	/**  
	 * No side-effects!
   */
	@Override
	protected BlockStatementSequence<? extends T> planFromElems(Variable<T> simpleName) 
  {
		final List<BlockStatement<?>> planList = 
      new LinkedList<BlockStatement<?>>(); //plan for public fields.
		final List<String> fieldList = 
      new LinkedList<String>(); //field names for which we have plans.
		
		for (Field field: getType().getFields()) {
      BlockStatement<?> planFromEq = getPlanFromEq(field.getName());
			if (planFromEq!=null) {
				planList.add(planFromEq);
				fieldList.add(field.getName());
			}
		}
    BlockStatement<?>[] plans = planList.toArray(new BlockStatement[planList.size()]);
		String[] fields = fieldList.toArray(new String[fieldList.size()]);
		return new ComplexVariableCreatingBlockStatementSequence<T>(
        testeeType, 
        block, 
        simpleName, 
        (Expression<? extends T>) JCrasherPlanner.instance().getRandom(getType(), testeeType), 
        plans,
        fields);
	}
}
